邏輯迴歸 (Logistic Regression) 是一種常見的分類模型,主要用於預測二元分類或多元分類,有別於先前的線性迴歸是用來預測無邊界的連數據值,而邏輯迴歸間單來說就是預測有邊界的不連續數值,如 [0, 1], [1, 2, 3]。
那邏輯回歸是如何運作? 其實不論是哪種邏輯迴歸,底層都是先透過線性迴歸來預測,只是分別透過不同的激活函數與損失函數來處理,但是邏輯迴歸一般來說還是比較常用於二元分類,來看看以下流程:
以上就是二元分類邏輯迴歸的原理,那麼我們來看看多元分類邏輯迴歸是如何處理
可以看出不同的邏輯迴歸,只是分別透過不同的激活函數與損失函數來處理,雖然邏輯迴歸可以用於多元分類,但是一般來說還是比較常用於二元分類。
這個案例開始為了讓讀者有更好的感覺模型的過程,會分別使用 sklearn 與 PyTorch 來建模。但是必須先聲明,無論是手動撰寫或是透過 PyTorch 來模擬出來,都不一定有辦法比 sklearn 提供的演算法來得更優秀,所以除非有特殊目的,否則使用 sklearn 提供的演算法效能與準確性都會較高。
因本篇實作的目的,是為了更好的體現出 Logistic Regression 的原理,所以說明會以 PyTorch 實作為主。
從以下範例可以看出來,使用 sklearn 提供的方法簡單又快速,但是很難直觀的看出來他的原理,就也是 sklearn 的缺點,高度封裝帶來的便利性,也導致很多細節無法操作控制。
from sklearn.datasets import make_classification
from sklearn.model_selection import train_test_split
from sklearn.linear_model import LogisticRegression
from sklearn.metrics import accuracy_score, classification_report
X, y = make_classification(n_samples=1000, n_features=5, random_state=42)
X_train, X_test, y_train, y_test = train_test_split(
X, y, test_size=0.2, random_state=42
)
model = LogisticRegression()
model.fit(X_train, y_train)
# 預測
y_train_pred = model.predict(X_train)
y_test_pred = model.predict(X_test)
# 評估
print(f"Accuracy: {accuracy_score(y_train, y_train_pred):.2f}")
print(classification_report(y_train, y_train_pred))
print(f"Accuracy: {accuracy_score(y_test, y_test_pred):.2f}")
print(classification_report(y_test, y_test_pred))
使用 PyTorch 來做同一件事情,明顯的可以感覺到,比 sklearn 複雜很多,但是我們也可以透過這段程式碼來窺探 Logistic Regression 的細節。
如前面所說的,因為 Logistic Regression 是先使用 Linear Regression 方程式預測出一個結果,再藉由 Sigmoid 函數逕行轉換。以下是用單層神經網絡,來模擬 Logistic Regression 的演算法,所以只定義 output layer 為線性輸出層 + Sigmoid function。
# 模型定義
class LogisticRegressionModel(nn.Module):
def __init__(self, input_dim):
super(LogisticRegressionModel, self).__init__()
self.linear = nn.Linear(input_dim, 1)
self.sigmoid = nn.Sigmoid() # 1 個輸出 (sigmoid 之前)
def forward(self, X):
return self.sigmoid(self.linear(X))
定義完模型的基礎架構後,來定義 Criterion 為 Binary Class Entropy 與 Optimizer 為 Batch Gradient Descent,讓整個架構符合我們前面介紹的核心概念。
# 設定 criterion 與 optimizer
criterion = nn.BCELoss()
optimizer = optim.SGD(model.parameters(), lr=0.01)
從訓練代碼搭配前述的模型架構邏輯,可以看出來這個步驟也與前面模型介紹時呼應:
# 訓練
n_epochs = 1000
for epoch in range(n_epochs):
model.train() # 告訴模型進入: 訓練模式
optimizer.zero_grad() # 梯度歸零,預設梯度是累加的 (accumulated),必須在每一回合開始前清空上一輪的梯度,否則梯度會疊加,導致錯誤的參數更新
y_pred = model(X_train_tensor) # 前向傳播 (forward pass)
loss = criterion(y_pred, y_train_tensor) # 使用定義好的損失函數來計算預測值和真實標籤之間的損失
loss.backward() # 反向傳播 (Backward Pass),這些梯度會儲存在每個參數的 .grad 屬性中
optimizer.step() # 根據梯度更新模型參數
if (epoch+1) % 100 == 0:
print(f"Epoch {epoch+1}/{n_epochs}, Loss: {loss.item():.4f}")
import torch
import torch.nn as nn
import torch.optim as optim
from sklearn.datasets import make_classification
from sklearn.preprocessing import StandardScaler
from sklearn.model_selection import train_test_split
from sklearn.metrics import accuracy_score, classification_report
import numpy as np
# 資料產生與前處理
features_size = 5
X, y = make_classification(n_samples=100000, n_features=features_size, random_state=42)
X_train, X_test, y_train, y_test = train_test_split(
X, y, test_size=0.2, random_state=42
)
scaler = StandardScaler()
X_train_scaler = scaler.fit_transform(X_train)
# 轉換為 tensor
X_train_tensor = torch.tensor(X_train_scaler, dtype=torch.float32)
y_train_tensor = torch.tensor(y_train.reshape(-1, 1), dtype=torch.float32)
# 模型定義
class LogisticRegressionModel(nn.Module):
def __init__(self, input_dim):
super(LogisticRegressionModel, self).__init__()
self.linear = nn.Linear(input_dim, 1)
self.sigmoid = nn.Sigmoid() # 1 個輸出 (sigmoid 之前)
def forward(self, X):
return self.sigmoid(self.linear(X))
model = LogisticRegressionModel(input_dim=features_size)
# 設定 criterion 與 optimizer
criterion = nn.BCELoss()
optimizer = optim.SGD(model.parameters(), lr=0.01)
# 訓練
n_epochs = 1000
for epoch in range(n_epochs):
model.train() # 告訴模型進入: 訓練模式
optimizer.zero_grad() # 梯度歸零,預設梯度是累加的 (accumulated),必須在每一回合開始前清空上一輪的梯度,否則梯度會疊加,導致錯誤的參數更新
y_pred = model(X_train_tensor) # 前向傳播 (forward pass)
loss = criterion(y_pred, y_train_tensor) # 使用定義好的損失函數來計算預測值和真實標籤之間的損失
loss.backward() # 反向傳播 (Backward Pass),這些梯度會儲存在每個參數的 .grad 屬性中
optimizer.step() # 根據梯度更新模型參數
if (epoch+1) % 100 == 0:
print(f"Epoch {epoch+1}/{n_epochs}, Loss: {loss.item():.4f}")
# 預測
X_test_scaler = scaler.transform(X_test)
X_test_tensor = torch.tensor(X_test_scaler, dtype=torch.float32)
y_test_tensor = torch.tensor(y_test.reshape(-1, 1), dtype=torch.float32)
model.eval()
with torch.no_grad(): # 停用自動微分 (autograd),不會追蹤張量的計算圖,減少記憶體消耗與加速推論,是測試與部署時的必備寫法,訓練需要追蹤梯度,測試不需要,這行就是明確告訴 PyTorch「這是測試」
# 預測機率
y_train_prob = model(X_train_tensor)
y_test_prob = model(X_test_tensor)
threshold = 0.5
y_train_pred = (y_train_prob > threshold).int().numpy()
y_test_pred = (y_test_prob > threshold).int().numpy()
# 評估
print(f"\n[Train Accuracy] {accuracy_score(y_train, y_train_pred):.2f}")
print(classification_report(y_train, y_train_pred))
print(f"\n[Test Accuracy] {accuracy_score(y_test, y_test_pred):.2f}")
print(classification_report(y_test, y_test_pred))
[Train Accuracy] 0.87
precision recall f1-score support
0 0.86 0.88 0.87 40060
1 0.88 0.85 0.86 39940
accuracy 0.87 80000
macro avg 0.87 0.87 0.87 80000
weighted avg 0.87 0.87 0.87 80000
[Test Accuracy] 0.87
precision recall f1-score support
0 0.85 0.88 0.87 9954
1 0.88 0.85 0.86 10046
accuracy 0.87 20000
macro avg 0.87 0.87 0.87 20000
weighted avg 0.87 0.87 0.87 20000
我們就直接以 PyTorh 執行結果來做分析,因為這個案例是直接使用假資料做,所以其實結果蠻漂亮的,大致上可以分析出一下的情況:
Logistic Regression 作為分類任務的入門模型,看似簡單,卻蘊含著機率思維、最佳化原理、以及模型評估等多層次概念。它在許多實務應用中仍佔有一席之地,尤其當我們追求的是高可解釋性與高效率的解法,例如信用評分、醫療預測或風險判斷等領域,Logistic Regression 往往仍是首選。
本篇除了以 sklearn 快速建模,也特別採用 PyTorch 從底層還原 Logistic Regression 的運作過程,目的不在於提升準確率,而是釐清這個模型「為什麼有效」、「如何運作」、「可以控制哪些面向」。這種深入理解將為後續進入更複雜的分類模型奠定紮實基礎。